package md.sv.dbu.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author MG
 */
public class PatternReplacer {
    public static final String COMPOUND_VAR_DELIMITER = ":";

    private static final Logger logger = LoggerFactory.getLogger(PatternReplacer.class);
    private final Pattern pattern;

    public PatternReplacer(String variableRegex) {
        this.pattern = Pattern.compile(variableRegex);
    }

    /**
     * Parse a brute variable. i.e. try to make an object from it, if it is a compound variable, otherwise return the original value
     *
     * @param variable          the variable name (potentially compound)
     * @param value             the variable value
     * @param dateFormatMap     a map of date formats (for parsing date compound variables)
     * @return                  the parsed variable in case it is compound, and the original value otherwise
     */
    public static Object parseVariable(String variable, String value, Map<String, DateFormat> dateFormatMap) {
        if (variable.contains(COMPOUND_VAR_DELIMITER)) {
            String[] dateVarName = variable.split(COMPOUND_VAR_DELIMITER);
            String dateFormat = dateVarName[1];
            if (dateFormat != null) {
                try {
                    return dateFormatMap.get(dateFormat).parse(value);
                } catch (ParseException e) {
                    logger.warn("Could not parse variable [{}] with value [{}]", variable, value);
                    return value;
                }
            }
        }

        return value;
    }

    /**
     * Returns the variable simple name, if it is a compound variable, or the original name for a simple one
     * @param variableName  the variable name to check
     * @return              the simple variable name
     */
    public static String getVariableSimpleName(String variableName) {
        if (variableName.contains(COMPOUND_VAR_DELIMITER)) {
            return variableName.split(COMPOUND_VAR_DELIMITER)[0];
        }

        return variableName;
    }

    /**
     * Attempts to replace all found variables with the map of defined variable values
     * @param input         the input StringBuilder to be processed
     * @param variables     the map of variables and their values
     * @return              the number of matches (variables) found, but not processed. <br/>
     *                      return = 0 - means that all variables processed ok (or no variables found). <br/>
     *                      return > 0 - means that this variables count has been matched, but no replacements for them
     *                      where found in the variables map.
     */
    public int replaceVariables(StringBuilder input, Map<String, ?> variables, Map<String, DateFormat> dateFormatMap) {
        Matcher matcher = pattern.matcher(input);
        int noFinds = 0;
        while (matcher.find()) {
            String varName = matcher.group(1);
            String dateFormat = null;
            if (varName.contains(COMPOUND_VAR_DELIMITER)) {
                String[] dateVarName = varName.split(COMPOUND_VAR_DELIMITER);
                varName = dateVarName[0];
                dateFormat = dateVarName[1];
            }
            Object var = variables.get(varName);
            if (var != null) {
                if (dateFormat != null) {
                    try {
                        input.replace(matcher.start(), matcher.end(), dateFormatMap.get(dateFormat).format(var));
                    } catch (Exception e) {
                        logger.warn("Exception: ", e);
                        input.replace(matcher.start(), matcher.end(), "");
                    }
                } else {
                    input.replace(matcher.start(), matcher.end(), var.toString());
                }
                matcher.reset();
            } else {
                logger.debug("Could not find replace for variable [{}]", matcher.group(1));
                input.replace(matcher.start(), matcher.end(), "");
                matcher.reset();
                noFinds++;
            }
        }
        return noFinds;
    }

    /**
     * Gets the variable set of an input character sequence
     *
     * @param input     The input Character sequence
     * @return          the variables Set
     */
    public Set<String> getVariablesSet(CharSequence input) {
        Set<String> vars = new HashSet<String>();
        Matcher matcher = pattern.matcher(input);
        while (matcher.find()) {
            vars.add(matcher.group(1));
        }

        return vars;
    }

    public String getVariableName(String variable) {
        Matcher matcher = pattern.matcher(variable);

        if (matcher.find()) {
            return matcher.group(1);
        }

        return null;
    }

    public String getVariable(String variableName) {
        String patternStr = pattern.toString();
        patternStr = patternStr.replaceAll("\\\\", "");
        return patternStr.substring(0, patternStr.indexOf('(')) + variableName + patternStr.substring(patternStr.lastIndexOf(')') + 1);
    }

    public boolean hasVariables(CharSequence input) {
        Matcher matcher = pattern.matcher(input);
        return matcher.find();
    }
}
